package com.tao.orders.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tao.base.dictionary.OrderPayStatus;
import com.tao.base.dictionary.OrderType;
import com.tao.base.dictionary.PayStatus;
import com.tao.base.exception.LearnHubException;
import com.tao.base.utils.IdWorkerUtils;
import com.tao.base.utils.QRCodeUtil;
import com.tao.messagesdk.model.po.MqMessage;
import com.tao.messagesdk.service.MqMessageService;
import com.tao.orders.config.AlipayConfig;
import com.tao.orders.config.PayNotifyConfig;
import com.tao.orders.mapper.OrdersGoodsMapper;
import com.tao.orders.mapper.OrdersMapper;
import com.tao.orders.mapper.PayRecordMapper;
import com.tao.orders.model.dto.AddOrderDto;
import com.tao.orders.model.dto.PayRecordDto;
import com.tao.orders.model.dto.PayStatusDto;
import com.tao.orders.model.po.Orders;
import com.tao.orders.model.po.OrdersGoods;
import com.tao.orders.model.po.PayRecord;
import com.tao.orders.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

/**
 * <p>
 *
 * </p>
 *
 * @author taohongrun
 * @since 2024/11/29
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements OrderService {
    @Autowired
    OrderServiceImpl orderServiceSelf;
    @Autowired
    OrdersGoodsMapper ordersGoodsMapper;
    @Autowired
    PayRecordMapper payRecordMapper;
    @Autowired
    OrdersMapper ordersMapper;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    MqMessageService mqMessageService;
    @Value("${pay.notifyHost}")
    String notifyHost;
    @Value("${pay.alipayURL}")
    String alipay;
    @Value("${pay.alipay.APP_ID}")
    String APP_ID;
    @Value("${pay.alipay.APP_PRIVATE_KEY}")
    String APP_PRIVATE_KEY;
    @Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")
    String ALIPAY_PUBLIC_KEY;
    @Override
    @Transactional
    public PayRecordDto createOrder(String userId, AddOrderDto addOrderDto) {
        //异常处理 一般为防止前端参数出错，比如数据不存在(数据库删除，缓存未删除，可能导致该情况)，不符合格式等（个人开发阶段可有可无），防止违反数据库约束（以前没见过，这里注意），以及防止业务逻辑出错
       //每当涉及增删改时都要异常处理违反数据库约束的情况

        //将保存订单以及保存订单详情可以封装
        Orders orders = saveOrder(userId,addOrderDto);

        //保存支付记录
        //由于这里要用到订单信息，因此保存订单时需要将返回结果从void更改为Orders
        PayRecord payRecord =savePayRecord(orders);

        //生成支付二维码
        QRCodeUtil qrCodeUtil = new QRCodeUtil();
        //支付二维码url
        log.info(":{}",notifyHost);
        log.info(":{}",alipay);
        String url = notifyHost + alipay + '?'+"payNo="+payRecord.getPayNo();
        log.info(":{}",url);
        String qrCode;
        try {
            qrCode = qrCodeUtil.createQRCode(url, 400, 400);
        } catch (IOException e) {
            throw new RuntimeException("二维码生成失败");
        }

        //由于要返回支付记录，需要将保存支付记录返回值从void改为PayRecord
        PayRecordDto payRecordDto = new PayRecordDto();
        BeanUtils.copyProperties(payRecord,payRecordDto);
        payRecordDto.setQrcode(qrCode);
        return payRecordDto;
    }

    //根据业务id（选课id）查询订单
    //查询唯一字段是否存在
    //由于以后可能有人会通过该字段获取数据，因此判断逻辑不在这里编写
    private Orders getOrderByBusinessId(String businessId){
         return lambdaQuery().eq(Orders::getOutBusinessId, businessId).one();
    }

    private Orders saveOrder(String userId,AddOrderDto addOrderDto){
        //增删改数据库要进行异常处理，防止违反数据库约束
        //数据库字段中 订单中的 选课id 唯一，表明 选课id 与订单为一对一关系，这里要防止同一个人的一门课（选课id）生成多个订单，即事先查看 选课id 是否已存在
        Orders orderByBusinessId = getOrderByBusinessId(addOrderDto.getOutBusinessId());
        if (orderByBusinessId!=null) throw new RuntimeException("该用户当前课程对应订单已存在");

        Orders orders = new Orders();
        orders.setId(IdWorkerUtils.getInstance().nextId());
        orders.setTotalPrice(addOrderDto.getTotalPrice());
        orders.setStatus(OrderPayStatus.NO_PAY);
        orders.setUserId(userId);
        orders.setOrderType(OrderType.BUY_COURSE);
        orders.setOrderName(addOrderDto.getOrderName());
        orders.setOrderDescrip(addOrderDto.getOrderDescrip());
        orders.setOrderDetail(addOrderDto.getOrderDetail());
        orders.setOutBusinessId(addOrderDto.getOutBusinessId());
        orderServiceSelf.save(orders);
        log.info("------------订单表数据插入成功------------");

        //插入订单明细表
        //前端传的的为json格式，需要转成所需的格式，这里所需为数组
        String orderDetailJson = addOrderDto.getOrderDetail();
        List<OrdersGoods> ordersGoodsList = JSON.parseArray(orderDetailJson, OrdersGoods.class);
        //遍历进行插入
        /*for (OrdersGoods ordersGoods : ordersGoodsList){
            OrdersGoods ordersGoods1 = new OrdersGoods();
            BeanUtils.copyProperties(ordersGoods,ordersGoods1);
            ordersGoods1.setOrderId(orders.getId());
            ordersGoodsMapper.insert(ordersGoods1);
        }*/
        //优化为for each 访问一次数据库
        ordersGoodsList.forEach(ordersGoods -> ordersGoods.setOrderId(orders.getId()));
        ordersGoodsMapper.insert(ordersGoodsList);


        return orders;
    }

    private PayRecord savePayRecord(Orders orders){
        //增删改数据库要进行异常处理，防止违反数据库约束
        //这里的数据库约束无需防范

        //当支付已存在订单时，仍需调用该接口，这里有防止重复支付的业务逻辑异常
        if(orders.getStatus().equals(OrderPayStatus.PAYED)) throw new RuntimeException("该订单已经支付，请勿重复支付");

        //添加支付记录
        PayRecord payRecord = new PayRecord();
        payRecord.setPayNo(IdWorkerUtils.getInstance().nextId());
        payRecord.setId(payRecord.getPayNo());
        payRecord.setOrderId(orders.getId());
        payRecord.setOrderName(orders.getOrderName());
        payRecord.setTotalPrice(orders.getTotalPrice());
        payRecord.setCurrency("CNY");
        payRecord.setCreateDate(LocalDateTime.now());
        payRecord.setStatus(PayStatus.NO_PAY);
        payRecord.setUserId(orders.getUserId());

        payRecordMapper.insert(payRecord);
        log.info("-----------------插入支付记录成功-----------------");
        return payRecord;
    }

    private PayRecord getPayRecordByPayno(String payNo){
        return  payRecordMapper.selectById(payNo);
    }
    @Override
    public String getBizContent(String payNo){
        PayRecord payRecord =getPayRecordByPayno(payNo);

        return "{" +
                "    \"out_trade_no\":\" "+ payNo + "\"," +
                "    \"total_amount\":"+ payRecord.getTotalPrice()+"," +
                "    \"subject\":\""+payRecord.getOrderName()+"\"," +
                "    \"product_code\":\"QUICK_WAP_WAY\"" +
                "  }";
    }

    public void exitPayNo(String payNo){
        PayRecord payRecord = payRecordMapper.selectById(payNo);
        if(payRecord == null) LearnHubException.throwMyException("该订单不存在");
    }

    @Override
    @Transactional
    public PayRecordDto queryPayResult(String payNo) {
        PayRecord payRecord = getPayRecordByPayno(payNo);
        if (payRecord == null) {
            LearnHubException.throwMyException("请重新点击支付获取二维码");
        }
        //支付状态
        String status = payRecord.getStatus();
        //如果支付成功直接返回
        if (PayStatus.PAYED.equals(status)) {
            PayRecordDto payRecordDto = new PayRecordDto();
            BeanUtils.copyProperties(payRecord, payRecordDto);
            return payRecordDto;
        }
        //从支付宝查询支付结果
        PayStatusDto payStatusDto = queryPayResultFromAlipay(payNo);
        //保存支付结果
        orderServiceSelf.saveAliPayStatus(payStatusDto);
        //重新查询支付记录
        payRecord = getPayRecordByPayno(payNo);
        PayRecordDto payRecordDto = new PayRecordDto();
        BeanUtils.copyProperties(payRecord, payRecordDto);
        return payRecordDto;

    }

    public PayStatusDto queryPayResultFromAlipay(String payNo){
        //========请求支付宝查询支付结果=============
        AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, "json", AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE); //获得初始化的AlipayClient
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", payNo);
        request.setBizContent(bizContent.toString());
        AlipayTradeQueryResponse response = null;
        try {
            response = alipayClient.execute(request);
            if (!response.isSuccess()) {
                LearnHubException.throwMyException("请求支付查询查询失败");
            }
        } catch (AlipayApiException e) {
            log.error("请求支付宝查询支付结果异常:{}", e.toString(), e);
            LearnHubException.throwMyException("请求支付查询查询失败");
        }

        //获取支付结果
        String resultJson = response.getBody();
        //转map
        Map resultMap = JSON.parseObject(resultJson, Map.class);
        Map alipay_trade_query_response = (Map) resultMap.get("alipay_trade_query_response");
        //支付结果
        String trade_status = (String) alipay_trade_query_response.get("trade_status");
        String total_amount = (String) alipay_trade_query_response.get("total_amount");
        String trade_no = (String) alipay_trade_query_response.get("trade_no");
        //保存支付结果
        PayStatusDto payStatusDto = new PayStatusDto();
        payStatusDto.setOut_trade_no(payNo);
        payStatusDto.setTrade_status(trade_status);
        payStatusDto.setApp_id(APP_ID);
        payStatusDto.setTrade_no(trade_no);
        payStatusDto.setTotal_amount(total_amount);
        return payStatusDto;

    }


    @Override
    public void saveAliPayStatus(PayStatusDto payStatusDto) {
       //支付流水号
       String payNo = payStatusDto.getOut_trade_no();
       PayRecord payRecord = getPayRecordByPayno(payNo);
       if (payRecord == null) {
           LearnHubException.throwMyException("支付记录找不到");
       }
       //支付结果
       String trade_status = payStatusDto.getTrade_status();
       log.debug("收到支付结果:{},支付记录:{}}", payStatusDto.toString(),payRecord.toString());
       if (trade_status.equals("TRADE_SUCCESS")) {

           //支付金额变为分
           Float totalPrice = payRecord.getTotalPrice() * 100;
           Float total_amount = Float.parseFloat(payStatusDto.getTotal_amount()) * 100;
           //校验是否一致
           if (!payStatusDto.getApp_id().equals(APP_ID) || totalPrice.intValue() != total_amount.intValue()) {
               //校验失败
               log.info("校验支付结果失败,支付记录:{},APP_ID:{},totalPrice:{}" ,payRecord.toString(),payStatusDto.getApp_id(),total_amount.intValue());
               LearnHubException.throwMyException("校验支付结果失败");
           }
           log.debug("更新支付结果,支付交易流水号:{},支付结果:{}", payNo, trade_status);
           PayRecord payRecord_u = new PayRecord();
           payRecord_u.setStatus(PayStatus.PAYED);//支付成功
           payRecord_u.setOutPayChannel("Alipay");
           payRecord_u.setOutPayNo(payStatusDto.getTrade_no());//支付宝交易号
           payRecord_u.setPaySuccessTime(LocalDateTime.now());//通知时间
           int update1 = payRecordMapper.update(payRecord_u, new LambdaQueryWrapper<PayRecord>().eq(PayRecord::getPayNo, payNo));
           if (update1 > 0) {
               log.info("更新支付记录状态成功:{}", payRecord_u.toString());
           } else {
               log.info("更新支付记录状态失败:{}", payRecord_u.toString());
               LearnHubException.throwMyException("更新支付记录状态失败");
           }
           //关联的订单号
           Long orderId = payRecord.getOrderId();
           Orders orders = orderServiceSelf.getById(orderId);
           if (orders == null) {
               log.info("根据支付记录[{}}]找不到订单", payRecord_u.toString());
               LearnHubException.throwMyException("根据支付记录找不到订单");
           }
           Orders order_u = new Orders();
           order_u.setStatus(OrderPayStatus.PAYED);//支付成功
           int update = ordersMapper.update(order_u, new LambdaQueryWrapper<Orders>().eq(Orders::getId, orderId));
           if (update > 0) {
               log.info("更新订单表状态成功,订单号:{}", orderId);
           } else {
               log.info("更新订单表状态失败,订单号:{}", orderId);
               LearnHubException.throwMyException("更新订单表状态失败");
           }

           //确认支付成功后，可以发送消息，实现异步更新其他服务
           //保存消息记录,参数1：支付结果通知类型，2: 业务id，3:业务类型
           MqMessage mqMessage = mqMessageService.addMessage("payresult_notify", orders.getOutBusinessId(), orders.getOrderType(), null);
           //通知消息
           notifyPayResult(mqMessage);

       }

   }

    @Override
    public void notifyPayResult(MqMessage message) {

        //1、消息体，转json
        String msg = JSON.toJSONString(message);
        //设置消息持久化
        Message msgObj = MessageBuilder.withBody(msg.getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();

        // 3.添加callback,配置消息确认回调
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (correlationData == null) {
                return;
            }

            String messageId = correlationData.getId();

            if (ack) {
                // 3.1 消息发送成功
                log.debug("通知支付结果消息发送成功, ID:{}", messageId);
                // 删除消息表中的记录
                mqMessageService.completed(Long.parseLong(messageId));
            } else {
                // 3.2 消息发送失败
                log.error("通知支付结果消息发送失败, ID:{}, 原因:{}", messageId, cause);
                // 可以添加重试或其他错误处理逻辑
            }
        });
        // 4.全局唯一的消息ID，需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(message.getId().toString());
        // 发送消息
        rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT, "", msgObj,correlationData);

    }

}
